﻿// Copyright (c) 2012-2013 fancidev
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.

using System;
using System.Collections.Generic;
using System.IO;

namespace Util.IO
{
    /// <summary>
    /// Contains information about the directory being scanned.
    /// </summary>
    public class ScanDirectoryInfo
    {
        /// <summary>
        /// Gets the directory being scanned.
        /// </summary>
        public DirectoryInfo Directory { get; private set; }

        /// <summary>
        /// Gets a number between 0.0 and 1.0 which indicates the overall
        /// scanning progress before processing this directory.
        /// </summary>
        public double ProgressBefore { get; private set; }

        /// <summary>
        /// Gets a number between 0.0 and 1.0 which indicates the overall
        /// scanning progress after processing this directory and all its
        /// subdirectories.
        /// </summary>
        public double ProgressAfter { get; private set; }

        /// <summary>
        /// Gets or sets the exception when trying to access this directory.
        /// </summary>
        public Exception Exception { get; set; }

        /// <summary>
        /// Creates a <code>ScanDirectoryInfo</code> object.
        /// </summary>
        /// <param name="directory">The directory being processed.</param>
        /// <param name="progressBefore">The progress before processing this
        /// directory.</param>
        /// <param name="progressAfter">The progress after processing this
        /// directory and all its subdirectories.</param>
        public ScanDirectoryInfo(
            DirectoryInfo directory, double progressBefore, double progressAfter)
        {
            this.Directory = directory;
            this.ProgressBefore = progressBefore;
            this.ProgressAfter = progressAfter;
            this.Exception = null;
        }
    }

    /// <summary>
    /// Provides extension methods related to file system directories.
    /// </summary>
    public static class DirectoryExtensions
    {
        /// <summary>
        /// Scans a directory recursively for subdirectories.
        /// </summary>
        /// <param name="rootDir">The directory to scan.</param>
        /// <returns>An enumerator through which the supplied directory
        /// and all its subdirectories are returned.</returns>
        /// <remarks>This method scans the given directory recursively. If an
        /// exception occurs when trying to access a directory, the exception
        /// is returned along with the directory in the enumerator, and the
        /// scan continues with the next directory.
        /// </remarks>
        public static IEnumerable<ScanDirectoryInfo> ScanDirectories(
            this DirectoryInfo rootDir)
        {
            // Use a stack to simulate recursive calls. This allows us to
            // implement the enumerator interface.
            Stack<ScanDirectoryInfo> candidates = new Stack<ScanDirectoryInfo>();
            if (rootDir != null)
            {
                ScanDirectoryInfo p = new ScanDirectoryInfo(rootDir, 0.0, 1.0);
                candidates.Push(p);
            }

            while (candidates.Count > 0)
            {
                ScanDirectoryInfo p = candidates.Pop();

                // Get subdirectories to detect whether dir is accessible.
                DirectoryInfo[] subdirs = null;
                try
                {
                    subdirs = p.Directory.GetDirectories();
                }
                catch (Exception ex)
                {
                    p.Exception = ex;
                }

                // Yield this directory.
                yield return p;

                // Add subdirectories to the queue.
                if (subdirs != null && subdirs.Length > 0)
                {
                    double progBegin = p.ProgressBefore;
                    double progEnd = p.ProgressAfter;
                    double progPerSubdir = (progEnd - progBegin) / subdirs.Length;
                    for (int i = subdirs.Length - 1; i >= 0; i--)
                    {
                        double begin = progBegin + i * progPerSubdir;
                        double end = progEnd - (subdirs.Length - i - 1) * progPerSubdir;
                        candidates.Push(new ScanDirectoryInfo(subdirs[i], begin, end));
                    }
                }
            }
        }
    }
}
